/**************************************************************************/ /**
 * @file     load_mv.c
 * @brief    播放 video / music 文件
 * @version  V3.2
 * @date     2022.03.11
******************************************************************************/
#include "jfif_parser.h"
#include "avi_parser.h"
#include "wav_parser.h"
#include "load_mv.h"

volatile uint8_t STB_Enable = 0;//0-未使能抑制    	1-已使能抑制(devdac.c)

//------------------------------------本地调试宏----------------------------------------//
//#define MV_DEBUG_ENABLE
#ifdef MV_DEBUG_ENABLE
#define MV_DEBUG(...) 				DEBUG(__VA_ARGS__)

#else
#define MV_DEBUG(...) 				;

#endif

//------------------------------------本地类型声明----------------------------------------//
/* ping pong buffer control structure */
typedef struct
{
	int16_t *buf;
	volatile uint8_t ping;
	volatile uint8_t pong;
} ppbuf_t; //乒乓双缓冲

//------------------------------------------数据缓冲区定义----------------------------------------------//
#if ARM_LINKER_ATTR //LV_MEM_ATTR自动分配
  uint8_t LV_MEM_ATTR JPEG_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)];
  uint8_t LV_MEM_ATTR Video_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)];
  uint8_t LV_MEM_ATTR Video_RGB_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)];

#elif ARM_LINKER_AUTO //是否由链接器自动分配地址空间
  uint8_t JPEG_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".SDRAM")));
  uint8_t Video_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".SDRAM")));
  uint8_t Video_RGB_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".SDRAM")));
#else //手动指定 存储区域起点 的 绝对地址

#if (480 == LCD_HDOT && 272 == LCD_VDOT) // 480*272

uint8_t JPEG_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".bss.ARM.__at_0x8007F800")));
uint8_t Video_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".bss.ARM.__at_0x800BF400")));
uint8_t Video_RGB_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".bss.ARM.__at_0x800FF000")));

#elif (480 == LCD_HDOT && 480 == LCD_VDOT) // 480*480

uint8_t JPEG_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".bss.ARM.__at_0x800E1000")));
uint8_t Video_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t) * 3 / 2] __attribute__((section(".bss.ARM.__at_0x80151800")));
uint8_t Video_RGB_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".bss.ARM.__at_0x801FA400")));

#elif ((800 == LCD_HDOT && 480 == LCD_VDOT) || (480 == LCD_HDOT && 800 == LCD_VDOT)) // 800*480

uint8_t JPEG_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".bss.ARM.__at_0x80177000")));
uint8_t Video_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".bss.ARM.__at_0x80232800")));
uint8_t Video_RGB_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".bss.ARM.__at_0x802EE000")));

#elif (1024 == LCD_HDOT && 600 == LCD_VDOT) // 1024*600

uint8_t JPEG_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".bss.ARM.__at_0x80258000")));
uint8_t Video_Decoder_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t)] __attribute__((section(".bss.ARM.__at_0x80384000")));
uint8_t Video_RGB_Buff[LV_HOR_RES_MAX * LV_VER_RES_MAX * sizeof(lv_color_t) / 2] __attribute__((section(".bss.ARM.__at_0x804B0000")));

#endif

#endif // ARM_LINKER_AUTO

//音频数据双缓冲 不可放到 SDRAM 内，因 DMA -> DAC 会与 LCD_DMA 竞争总线带宽，导致刷屏显示异常晃动
int16_t Audio_PCM_Buff[PCM_BUFSIZE * 2];

//------------------------------------全局共享变量定义----------------------------------------//
//全局播放音视频句柄
play_mv_t Play_Task_Handler = {
	.fsm_state = fsm_rt_waiting,
	.frame_flag = 0,
	.audio_flag = 0,
};

volatile uint32_t Video_Finish_Time = 0; //视频重复播放次数 :	0/第一次未播完    	(N >= 1)/播完 N 次
uint32_t Video_Time_Max = 0;			 //视频重复播放最大次数(uint32_t有效范围) 0xFFFFFFFF

//视频流
jfif_info_t Video_Info;		// Video流图片信息结构体
jpeg_outset_t Video_OutSet; // Video流JPEG解码输出设定

//音频流
ppbuf_t PP_Buff = {&Audio_PCM_Buff[0], 0, 1}; // init ping-pong buffer

//-----------------------------------------本地静态变量定义-------------------------------------------//
/* 流媒体播放时的相关信息 */
FIL File_Handle;						//文件句柄
static FRESULT F_Res = FR_OK;			// FATFS 返回
static uint32_t Read_Len = 0;			//读取文件的实际数据量
static uint32_t Restart_Stream_ID = 0;	//流起始数据类型
static uint32_t Restart_Offset = 0;		//流起始数据偏移量
static uint32_t Restart_StreamSize = 0; //流起始的数据大小
static uint8_t First_Stream_Flag = 0;	//流起始的帧类型标志 0-音频帧在前	1-视频帧在前

// static uint8_t Stream_Buf[8]; //音频流ID信息

//-----------------------------------------------函数定义------------------------------------------------//
/* BTIMR1 —— 适应视频帧率 */
void BTIMR1_Handler(void)
{
	TIMR_INTClr(BTIMR1);
	Play_Task_Handler.frame_flag = 0;
}

/* DMA_CH0 <=> DAC —— 适应音频采样率 */
void DMA_Handler(void)
{
	if (DMA_CH_INTStat(DMA_CH0, DMA_IT_DONE))
	{
		DMA_CH_INTClr(DMA_CH0, DMA_IT_DONE);
		Play_Task_Handler.audio_flag = 0;
	}
}

/**
  \brief		改变视频播放位置
  \param [in]	lcd_layer	: 图层
				xs / ys		: 起始位置
  \return       /
  \note    		视频源大小一般不可改变, 可使新设置的播放区域大小自动适配源的大小( Avix )，请在 play_mv_init(mv) 之后调用
 */
void video_change_post(uint8_t lcd_layer, uint16_t xs, uint16_t ys)
{
#if 1 //参数校验
	if (xs >= LV_HOR_RES_MAX || ys >= LV_VER_RES_MAX)
		return; //错误抛出
#else
	xs = (xs >= LV_HOR_RES_MAX) ? (LV_HOR_RES_MAX - Avix.Width) : xs;
	ys = (ys >= LV_VER_RES_MAX) ? (LV_VER_RES_MAX - Avix.Height) : ys;
#endif

	//图层大小自适应视频源
	uint16_t xe = ((xs + Avix.Width - 1) > LV_HOR_RES_MAX) ? (LV_HOR_RES_MAX - 1) : (xs + Avix.Width - 1);
	uint16_t ye = ((ys + Avix.Height - 1) > LV_VER_RES_MAX) ? (LV_VER_RES_MAX - 1) : (ys + Avix.Height - 1);

	LCD->L[lcd_layer].WHP = (xs << LCD_WHP_STA_Pos) |
							(xe << LCD_WHP_STP_Pos);

	LCD->L[lcd_layer].WVP = (ys << LCD_WVP_STA_Pos) |
							(ye << LCD_WVP_STP_Pos);

	LCD->L[lcd_layer].LLEN = (xe - xs); //差必须为奇数

	LCD->CR |= (1 << LCD_CR_VBPRELOAD_Pos); // 1 帧显示开始时，将层配置寄存器的值加载到层工作寄存器中

	//更新起始位置信息
	Play_Task_Handler.post[0] = xs;
	Play_Task_Handler.post[1] = ys;
}

/**
  \brief		改变视频图层透明度
  \param [in]	lcd_layer	: 图层
				alpha		: 透明度
  \return       /
  \note    		图层 x 的 constant alpha，用于 blending。
				Blending 公式：
				BC = (C * CONSTA + Cs * (255 - CONSTA))/255
				BC: blended color
				C : current layer color
				Cs: subjacent layers blended color
				有如下几种 blending 情况：
				图层 1 和图层 2 都使能时：先将图层 1 和背景颜色进行 blending，然后将图层 2 和
				blending 后的图层进行 blending。
				图层 1 或图层 2 仅有一个图层使能时：将被使能的图层和背景颜色进行 blending。
				两个图层都不使能时：仅显示背景色。
 */
void video_change_alpha(uint8_t lcd_layer, uint8_t alpha)
{
	LCD->L[lcd_layer].LCR &= ~(0xFF << LCD_LCR_ALPHA_Pos);
	LCD->L[lcd_layer].LCR |= (alpha << LCD_LCR_ALPHA_Pos);

	LCD->CR |= (1 << LCD_CR_VBPRELOAD_Pos); // 1 帧显示开始时，将层配置寄存器的值加载到层工作寄存器中
}

/**
  \brief		改变视频输出格式
  \param [in]	format	: 解码输出格式，可取值JPEG_OUT_YUV、JPEG_OUT_YUVsp、JPEG_OUT_XRGB888、...详见 swm341_jpeg.h
				dither	: RGB565 dithering enable
  \return       /
  \note    		仅 format 为 JPEG_OUT_RGB565 时，dither 有效
 */
void video_change_format(uint8_t format, uint8_t dither)
{
	Video_OutSet.format = format;
	Video_OutSet.dither = (JPEG_OUT_RGB565 == format) ? dither : 0; // RGB565 dithering enable
}

/**
  \brief		暂停播放
  \param [in]	mv	: 播放任务句柄
  \return       /
  \note    		仅实现音频
 */
void pause_mv(play_mv_t *mv)
{
	if (fsm_rt_waiting != mv->fsm_state)
	{
		TIMR_Halt(TIMR0); //暂停播放音频

		/*
		if (0 != ((LCD->L[VIDEO_LAYER].LCR) >> LCD_LCR_EN_Pos))
		{
			TIMR_Stop(BTIMR1); //停止播放视频流
			//TIMR (0 ~ 4)定时器可以使用固件库的暂停/恢复函数, 其他定时器则不能

			//while (JPEG_DecodeBusy(JPEG)) //阻塞等待解码完毕
				//__NOP();
		}*/
	}
}

/**
  \brief		恢复播放
  \param [in]	mv	: 播放任务句柄
  \return       /
  \note    		仅实现音频
 */
void restore_mv(play_mv_t *mv)
{
	if (fsm_rt_waiting != mv->fsm_state)
	{
		TIMR_Resume(TIMR0);
		/*
		if (0 != ((LCD->L[VIDEO_LAYER].LCR) >> LCD_LCR_EN_Pos))
		{
			TIMR_Init(BTIMR1, TIMR_MODE_TIMER, CyclesPerUs, Avix.SecPerFrame, 1); //按视频帧率播放
			TIMR_Start(BTIMR1);                                                   //重新启动播放
		}*/
	}
}

/**
  \brief		关闭播放
  \param [in]	mv	: 播放任务句柄
  \return       fsm_rt_t : 播放状态
  \note    		根据当前播放源类型以关闭相关外设
 */
fsm_rt_t close_mv(play_mv_t *mv)
{
	if (fsm_rt_waiting != mv->fsm_state) //当前正在播放
	{
		if (VIDEO == mv->type) //视频流
		{
			if (Avix.AudioBufSize > 0) //视频源有音轨
			{
				dac_deinit();
			}

			TIMR_Stop(BTIMR1);	   // Stop Video
			f_close(&File_Handle); //关闭文件流

			if (0 != ((LCD->L[VIDEO_LAYER].LCR) >> LCD_LCR_EN_Pos))
			{
				LCD->L[VIDEO_LAYER].LCR = (0 << LCD_LCR_EN_Pos); //配置 LCD 第2层 失能
				LCD->CR |= (1 << LCD_CR_VBPRELOAD_Pos);			 //立即加载配置, 使其生效
			}
			MV_DEBUG("video stream over time = [%d], Max_Time = [%d]\r\n", Video_Finish_Time, Video_Time_Max);
			Video_Finish_Time = 0; //清零重复播放计数值
		}
		else if (MUSIC == mv->type) //音频流
		{
#if (!MUSIC_SDRAM_ENABLE)
			f_close(&File_Handle); //关闭文件流
#endif
			dac_deinit();
		}
	}
	return fsm_rt_waiting;
}

#if ((0== LVGL_DISP_MODE) && defined(LVGL_ROTE90))//软件转90度且用软件映射显示屏时
  //显示用软件转90度处理
  void DispSoftRote90(unsigned short x,unsigned short y,//投放到显示屏上的起始位置
                      unsigned short w, unsigned short h,//图像区域大小
                      lv_color_t *cSource);//图像块起始
#endif


/**
  \brief		初始化播放任务
  \param [in]	mv		 	: 播放任务句柄
				file_path 	: 文件路径 \\ The variable to which the pointer points must not be released until the function ends the called procedure
  \return       fsm_rt_t 	: 播放状态
  \note    	    \
 */
fsm_rt_t play_mv_init(play_mv_t *mv, const char *file_path)
{
	mv->frame_flag = 1; // avi一帧结束标志 		=> 	0/空闲		      	1/忙碌

	//根据文件拓展后缀名以区分
	if (NULL != strstr(file_path, ".avi")) // AVI 视频(*.avi)
		mv->type = VIDEO;
	else if (NULL != strstr(file_path, ".wav")) // WAV 音频(*.wav)
		mv->type = MUSIC;

	//------------------------------------根据文件类型进行解析播放----------------------------------------//
	if (VIDEO == mv->type) //视频文件 (*.avi) 
	{
		F_Res = f_open(&File_Handle, file_path, FA_OPEN_EXISTING | FA_READ);
		if (FR_OK != F_Res)
		{
			MV_DEBUG("[ERROR] : video open file fail!\r\n");
			return fsm_rt_open_err;
		}
		// 读取 AVI 文件头
		F_Res = f_read(&File_Handle, Video_Decoder_Buff, sizeof(Video_Decoder_Buff), &Read_Len);
		if (FR_OK != F_Res || 0 == Read_Len)
		{
			MV_DEBUG("[ERROR] : video read file fail!\r\n");
			f_close(&File_Handle);
			return fsm_rt_read_err;
		}
		// 对 AVI 文件头进行解析
		if (AVI_OK != avi_init((uint8_t *)Video_Decoder_Buff, Read_Len))
		{
			MV_DEBUG("[ERROR] : avi_init error!\r\n");
			f_close(&File_Handle);
			return fsm_rt_parser_err;
		}
		// 寻找 movi ID
		Restart_Offset = avi_srarch_id((uint8_t *)Video_Decoder_Buff, Read_Len, "movi");
		if (0 == Restart_Offset)
		{
			MV_DEBUG("[ERROR] : srarch movi id is error\r\n");
			f_close(&File_Handle);
			return fsm_rt_parser_err;
		}
		// 获取第一帧流信息
		if (AVI_OK != avi_get_streaminfo((uint8_t *)(Video_Decoder_Buff + Restart_Offset + 4)))
		{
			MV_DEBUG("[ERROR] : avi_get_streaminfo error!\r\n");
			f_close(&File_Handle);
			return fsm_rt_parser_err;
		}
		MV_DEBUG("fps:%d.\r\n", 1000 / (Avix.SecPerFrame / 1000));
		MV_DEBUG("Avix.StreamSize:%d.\r\n", Avix.StreamSize);

		//保存播放时的起始信息，便于重新播放
		// Restart_Offset
		Restart_Stream_ID = Avix.StreamID;	  //流起始数据类型
		Restart_StreamSize = Avix.StreamSize; //流起始的数据大小
#if ((0 != LVGL_DISP_MODE) || !defined(LVGL_ROTE90))//软件转90度且用软件映射显示屏时
		/*
		if (LV_HOR_RES_MAX == Avix.Width && LV_VER_RES_MAX == Avix.Height) //全屏默认: LCD_LAYER_1 避免混合而导致刷图速率降低，引起屏幕闪烁
		{
			SDRAM_Handle.cur = KEEP_LCD_ALIGNMENT(SDRAM_Handle.cur); //保持字对齐
			//配置显示界面
			LCD->L[LCD_LAYER_1].ADDR = SDRAM_Handle.cur;
			LCD->CR |= (1 << LCD_CR_VBPRELOAD_Pos); // 1 帧显示开始时，将层配置寄存器的值加载到层工作寄存器中
		}
		else //播放局部区域视频使用图层 2 进行混合叠图应用
		*/
		{
			//配置显示界面
			LCD_LayerInitStructure LCD_LayerInStruct;
			LCD_LayerInStruct.Alpha = 0xFF; //默认不透明

			//起始位置
			LCD_LayerInStruct.HStart = (mv->post[0] >= LV_HOR_RES_MAX) ? LV_HOR_RES_MAX - Avix.Width : mv->post[0];
			LCD_LayerInStruct.VStart = (mv->post[1] >= LV_VER_RES_MAX) ? LV_VER_RES_MAX - Avix.Height : mv->post[1];
			// Stop 必须大于 Start, 且 HStart - HStop必须为奇数
			LCD_LayerInStruct.HStop = ((LCD_LayerInStruct.HStart + Avix.Width - 1) > LV_HOR_RES_MAX) ? (LV_HOR_RES_MAX - 1) : (LCD_LayerInStruct.HStart + Avix.Width - 1);
			LCD_LayerInStruct.VStop = ((LCD_LayerInStruct.VStart + Avix.Height - 1) > LV_VER_RES_MAX) ? (LV_VER_RES_MAX - 1) : (LCD_LayerInStruct.VStart + Avix.Height - 1);
			LCD_LayerInStruct.DataSource = (uint32_t)Video_RGB_Buff; //数据源地址
			LCD_LayerInit(LCD, VIDEO_LAYER, &LCD_LayerInStruct);
		}
#endif //((0 != LVGL_DISP_MODE) || !defined(LVGL_ROTE90))
    
		//配置解码选项
		//memset(Video_RGB_Buff, 0, sizeof(Video_RGB_Buff)); //初始化黑色背景
		Video_OutSet.format = JPEG_OUT_RGB565;
		Video_OutSet.dither = 0; // RGB565 dithering enable
		
		//Video_OutSet.RGBAddr = (LV_HOR_RES_MAX == Avix.Width && LV_VER_RES_MAX == Avix.Height) ? SDRAM_Handle.cur : (uint32_t)Video_RGB_Buff;

		Video_OutSet.RGBAddr = (uint32_t)Video_RGB_Buff;
		
		f_lseek(&File_Handle, Restart_Offset + 12); //跳过标志ID,读地址偏移到流数据开始处

		if (Avix.AudioBufSize > 0) //视频源有音轨
		{
			// TIMR0 用于控制按音频采样率播放音频，每秒中断 Avix.SampleRate 次
			TIMR_Init(TIMR0, TIMR_MODE_TIMER, CyclesPerUs, 1000000 / Avix.SampleRate, 0);
			dac_dma_init((uint16_t*)&PP_Buff.buf[PP_Buff.ping * PCM_BUFSIZE], Avix.AudioBufSize / sizeof(uint16_t));

			if (AVI_AUDS_FLAG == Avix.StreamID) //音频帧在前
			{
				F_Res = f_read(&File_Handle, &PP_Buff.buf[PP_Buff.pong * PCM_BUFSIZE], Avix.StreamSize, &Read_Len);
				if (FR_OK != F_Res || 0 == Avix.StreamSize || 0 == Read_Len) //判断当前帧有无音轨数据
				{
					MV_DEBUG("video music [f_read] over!\r\n");
					return fsm_rt_cpl;
				}
				// wav 文件中解析出的音频数据是 int16_t，需要加上 0x8000 转换成 uint16_t 发送给 dac
				for (uint32_t i = 0; i < Avix.StreamSize / sizeof(uint16_t); ++i)
				{
					PP_Buff.buf[PP_Buff.pong * PCM_BUFSIZE + i] += 32768;
				}
				uint8_t Stream_Buf[8]; //音频流ID信息
				F_Res = f_read(&File_Handle, Stream_Buf, 8, &Read_Len);
				if (AVI_OK != avi_get_streaminfo(Stream_Buf)) //读取下一帧 流标志
				{
					MV_DEBUG("video music over!\r\n");
					return fsm_rt_cpl;
				}
				mv->audio_flag = 1;	   // dac dma传输完成标志	=>	0/空闲		      	1/忙碌
				First_Stream_Flag = 0; //重置

				TIMR_Start(TIMR0); //启动 DAC_DMA 触发源
			}
			else //视频帧在前
			{
				mv->audio_flag = 0;	   // dac dma传输完成标志	=>	0/空闲		      	1/忙碌
				First_Stream_Flag = 1; //标记视频帧在前

				// TIMR_Start(TIMR0);//启动 DAC_DMA 触发源
			}
		}
		// BTIMR1 用于控制帧率，每 Avix.SecPerFrame us 中断一次
		TIMR_Init(BTIMR1, TIMR_MODE_TIMER, CyclesPerUs, Avix.SecPerFrame, 1);
		TIMR_Start(BTIMR1);
	}
	else if (MUSIC == mv->type) //音频文件 (*.wav)
	{
#if (!MUSIC_SDRAM_ENABLE)
		uint8_t wav_head_buf[512];

		F_Res = f_open(&File_Handle, file_path, FA_OPEN_EXISTING | FA_READ);
		if (FR_OK != F_Res)
		{
			MV_DEBUG("[ERROR] : music open file fail!\r\n");
			return fsm_rt_open_err;
		}
		// 读取 wav 文件头
		F_Res = f_read(&File_Handle, wav_head_buf, sizeof(wav_head_buf), &Read_Len); //读取512字节头数据
		if (FR_OK != F_Res || 0 == Read_Len)
		{
			MV_DEBUG("[ERROR] : music read file fail!\r\n");
			f_close(&File_Handle);
			return fsm_rt_parser_err;
		}
		// 对文件头进行解析
		if (0 != wav_init((uint8_t *)wav_head_buf, Read_Len))
		{
			MV_DEBUG("[ERROR] : wav_init error!\r\n");
			f_close(&File_Handle);
			return fsm_rt_parser_err;
		}
		//跳过文件头
		f_lseek(&File_Handle, Wavx.datastart);

		/*------------------------------------读取实际上的第一个音轨---------------------------------------*/
		F_Res = f_read(&File_Handle, &PP_Buff.buf[PP_Buff.ping * PCM_BUFSIZE], PCM_BUFSIZE * sizeof(uint16_t), &Read_Len);
		if (FR_OK != F_Res || 0 == Read_Len) //结束
		{
			MV_DEBUG("wav music over!\r\n");
			return fsm_rt_cpl;
		}
		if (Read_Len < PCM_BUFSIZE * sizeof(uint16_t)) //不够数据了,补充0
		{
			for (uint32_t i = Read_Len / sizeof(uint16_t); i < PCM_BUFSIZE - Read_Len / sizeof(uint16_t); i++)
				PP_Buff.buf[PP_Buff.ping * PCM_BUFSIZE + i] = 0;
		}
		// wav 文件中解析出的音频数据是 int16_t，需要加上 0x8000 转换成 uint16_t 发送给 dac
		for (uint32_t i = 0; i < PCM_BUFSIZE; ++i)
		{
			PP_Buff.buf[PP_Buff.ping * PCM_BUFSIZE + i] += 32768;
		}
#else
		uint32_t wav_index = 0;
		for (; wav_index < File_Table.wav_num; ++wav_index) //遍历寻找对应音频
		{
			if (NULL != strstr(file_path, File_Table.wav[wav_index].f_name))
				break;
		}
		if (wav_index >= File_Table.wav_num) //索引有效性验证
			return fsm_rt_err;
		//配置 wav_sdram 播放所需信息
		Wavx.samplerate = File_Table.wav[wav_index].sample_rate;		   //采样率
		Wavx.datastart = (uint32_t)(File_Table.wav[wav_index].data_start); //音频数据起始地址
		Wavx.datasize = File_Table.wav[wav_index].data_size;			   //音频数据大小

		Read_Len = 0;
		/*------------------------------------读取实际上的第一个音轨---------------------------------------*/
		// wav 文件中解析出的音频数据是 int16_t，需要加上 0x8000 转换成 uint16_t 发送给 dac
		//在存储至 SPI-Flash 时已经做了这步操作
		memcpy(&PP_Buff.buf[PP_Buff.ping * PCM_BUFSIZE], (uint16_t *)(Wavx.datastart + Read_Len), PCM_BUFSIZE * sizeof(uint16_t));
		Read_Len += (PCM_BUFSIZE * sizeof(uint16_t));
#endif
		// TIMR0 用于控制按音频采样率播放音频，每秒中断 Wavx.samplerate 次
		TIMR_Init(TIMR0, TIMR_MODE_TIMER, CyclesPerUs, 1000000 / Wavx.samplerate, 0);
		dac_dma_init((uint16_t*)&PP_Buff.buf[PP_Buff.ping * PCM_BUFSIZE], PCM_BUFSIZE);
		mv->audio_flag = 1; // dac dma传输完成标志	=>	0/空闲		      	1/忙碌
		TIMR_Start(TIMR0);	//启动 DAC_DMA 触发源
	}
  //Boot_Video_Flag = false; //初始化触模退出
  mv->FrameCount = 0;
	return fsm_rt_on_going;
}

/**
  \brief		播放任务
  \param [in]	mv		 : 播放任务句柄
  \return       fsm_rt_t : 播放状态
  \note    		需要在 play_mv_init(mv) 后被调用
 */
fsm_rt_t play_mv(play_mv_t *mv)
{
	if (VIDEO == mv->type && AVI_VIDS_FLAG == Avix.StreamID) //图像
	{
		if (0 == mv->frame_flag) //等待帧时间到达
		{
			mv->frame_flag = 1; //在 BTIMR1 的中断里面设置为 0

			//如果准备解码下一帧图片时，上一帧图片还没有解码完成，会在此等待，
			//最好不要出现这种情况，如果出现这种情况，表示MCU处理不过来，应降低播放视频的帧率
			while (0 != JPEG_DecodeBusy(JPEG))
		 ; //解码器空闲可用
      
			F_Res = f_read(&File_Handle, Video_Decoder_Buff, Avix.StreamSize + 8, &Read_Len); //读入整帧+下一数据流ID信息
			if (0 != Avix.StreamSize)														  //当前帧有图像数据,则进行解码显示
			{
        #if ((0== LVGL_DISP_MODE) && defined(LVGL_ROTE90))//软件转90度且用软件映射显示屏时,需copy
        //视频图像太大时，实测丢帧，声音也不对了， 策略为：图像上次解码，下次送显
        //注：从SD读时，只有速度原来的2/3, FLASH未尝试
        if((Avix.Width < (LV_HOR_RES_MAX / 3 * 2)) || (mv->FrameCount & 0x01)){
            jfif_parse(Video_Decoder_Buff, Avix.StreamSize, &Video_Info);
            JPEG_Decode(JPEG, &Video_Info, &Video_OutSet);        
        }
        if((Avix.Width < (LV_HOR_RES_MAX / 3 * 2)) || !(mv->FrameCount & 0x01)){
            DispSoftRote90((mv->post[0] >= LV_HOR_RES_MAX) ? LV_HOR_RES_MAX - Avix.Width : mv->post[0],
                  (mv->post[1] >= LV_VER_RES_MAX) ? LV_VER_RES_MAX - Avix.Height : mv->post[1],
                   Video_Info.Width, Video_Info.Height,//图像区域大小
                  (lv_color_t*)Video_RGB_Buff);//图像块起始   
        }
        #else
          jfif_parse(Video_Decoder_Buff, Avix.StreamSize, &Video_Info);
          JPEG_Decode(JPEG, &Video_Info, &Video_OutSet);         
        #endif
        mv->FrameCount++;
			}
			if (AVI_OK != avi_get_streaminfo(Video_Decoder_Buff + Avix.StreamSize)) //读取下一帧 流标志
			{
				Video_Finish_Time++; //累计重复播放次数
				if (Video_Finish_Time < Video_Time_Max)
				{
					Avix.StreamID = Restart_Stream_ID;
					Avix.StreamSize = Restart_StreamSize;
					f_lseek(&File_Handle, Restart_Offset + 12); //跳过标志ID,读地址偏移到流数据开始处
				}
				else
				{
					MV_DEBUG("frame over!\r\n");
					while (0 != mv->audio_flag)
						; //此时 DAC_DMA 源 有可能已经切换,阻塞等待播放完毕
					return fsm_rt_cpl;
				}
			}
		}
	}
	else //音轨
	{
		if (0 == mv->audio_flag) //等待上一轨音频播放完成
		{
			mv->audio_flag = 1; //在 DMA_CH0 中断内置 0

/* STB抑制 */
#if 0 
			if (0 == Blank_Init_Music)//跳过第一帧音轨
			{
				//stb_timr_start();//跳过第一帧音轨后再跳过固定时长
				GPIO_ClrBit(GPIOM, PIN3);//置低
			}
			Blank_Init_Music = (Blank_Init_Music == 2) ? 0 : 1;//跳过空白帧
#else
			if (0 != STB_Enable)
			{
				STB_Enable = 0;
				GPIO_ClrBit(GPIOM, PIN3); //置低
			}
			// stb_timr_start();//跳过固定时长
#endif
			if (VIDEO == mv->type && AVI_AUDS_FLAG == Avix.StreamID) //视频流中的音轨
			{
				F_Res = f_read(&File_Handle, &PP_Buff.buf[PP_Buff.pong * PCM_BUFSIZE], Avix.StreamSize, &Read_Len);
				if (FR_OK != F_Res || 0 == Avix.StreamSize || 0 == Read_Len) //判断当前帧有无音轨数据
				{
					MV_DEBUG("video music [f_read] over!\r\n");
					return fsm_rt_cpl;
				}
				// wav 文件中解析出的音频数据是 int16_t，需要加上 0x8000 转换成 uint16_t 发送给 dac
				for (uint32_t i = 0; i < Avix.StreamSize / sizeof(uint16_t); ++i)
				{
					PP_Buff.buf[PP_Buff.pong * PCM_BUFSIZE + i] += 32768;
				}
				// 由于上述转换过程中，直接播放会有卡顿，切换 ping 和 pong 会有改善
				PP_Buff.ping = PP_Buff.ping ^ PP_Buff.pong;
				PP_Buff.pong = PP_Buff.pong ^ PP_Buff.ping;
				PP_Buff.ping = PP_Buff.ping ^ PP_Buff.pong;
				//切换 DAC_DMA 源
				DMA->CH[DMA_CH0].SRC = (uint32_t)&PP_Buff.buf[PP_Buff.ping * PCM_BUFSIZE];

				if (0 != First_Stream_Flag) //第一帧为视频图像 (AVI_VIDS_FLAG == Restart_Stream_ID)
				{
					First_Stream_Flag = 0; //重置
					TIMR_Start(TIMR0);	   //启动 DAC_DMA 触发源
				}

				uint8_t Stream_Buf[8]; //音频流ID信息
				F_Res = f_read(&File_Handle, Stream_Buf, 8, &Read_Len);
				if (AVI_OK != avi_get_streaminfo(Stream_Buf)) //读取下一帧 流标志
				{
					Video_Finish_Time++; //累加重复播放次数
					if (Video_Finish_Time < Video_Time_Max)
					{
						Avix.StreamID = Restart_Stream_ID;
						Avix.StreamSize = Restart_StreamSize;
						f_lseek(&File_Handle, Restart_Offset + 12); //跳过标志ID,读地址偏移到流数据开始处
					}
					else
					{
						MV_DEBUG("video music over!\r\n");
						while (0 != mv->audio_flag)
							; //此时 DAC_DMA 源 已切换,阻塞等待播放完毕
						return fsm_rt_cpl;
					}
				}
			}
			else if (MUSIC == mv->type) //音频
			{
#if MUSIC_SDRAM_ENABLE						   //音频数据存放至 SDRAM
				if (Read_Len >= Wavx.datasize) //结束
				{
					MV_DEBUG("wav_sdram music over!\r\n");
					return fsm_rt_cpl;
				}
				else if (Read_Len + PCM_BUFSIZE * sizeof(uint16_t) > Wavx.datasize) //不够数据了,补充 0
				{
					memcpy(&PP_Buff.buf[PP_Buff.pong * PCM_BUFSIZE], (uint16_t *)(Wavx.datastart + Read_Len), Wavx.datasize - Read_Len);
					for (uint32_t i = (Wavx.datasize - Read_Len) / sizeof(uint16_t); i < (PCM_BUFSIZE - (Wavx.datasize - Read_Len) / sizeof(uint16_t)); ++i)
						PP_Buff.buf[PP_Buff.pong * PCM_BUFSIZE + i] = 0;
				}
				else
				{
					memcpy(&PP_Buff.buf[PP_Buff.pong * PCM_BUFSIZE], (uint16_t *)(Wavx.datastart + Read_Len), PCM_BUFSIZE * sizeof(uint16_t));
				}
				Read_Len += (PCM_BUFSIZE * sizeof(uint16_t)); //累计读取数据量 递增
				// wav 文件中解析出的音频数据是 int16_t，需要加上 0x8000 转换成 uint16_t 发送给 dac
				for (uint32_t i = 0; i < PCM_BUFSIZE; ++i)
				{
					PP_Buff.buf[PP_Buff.pong * PCM_BUFSIZE + i] += 32768;
				}
				// 由于上述转换过程中，直接播放会有卡顿，切换 ping 和 pong 会有改善
				PP_Buff.ping = PP_Buff.ping ^ PP_Buff.pong;
				PP_Buff.pong = PP_Buff.pong ^ PP_Buff.ping;
				PP_Buff.ping = PP_Buff.ping ^ PP_Buff.pong;
				//切换 DAC_DMA 源
				DMA->CH[DMA_CH0].SRC = (uint32_t)&PP_Buff.buf[PP_Buff.ping * PCM_BUFSIZE];

#else //音频数据存放至 SPI-Flash
				F_Res = f_read(&File_Handle, &PP_Buff.buf[PP_Buff.pong * PCM_BUFSIZE], PCM_BUFSIZE * sizeof(uint16_t), &Read_Len);
				if (FR_OK != F_Res || 0 == Read_Len) //结束
				{
					MV_DEBUG("wav_file music over!\r\n");
					return fsm_rt_cpl;
				}
				if (Read_Len < PCM_BUFSIZE * sizeof(uint16_t)) //不够数据了,补充0
				{
					for (uint32_t i = Read_Len / sizeof(uint16_t); i < PCM_BUFSIZE - Read_Len / sizeof(uint16_t); ++i)
						PP_Buff.buf[PP_Buff.pong * PCM_BUFSIZE + i] = 0;
				}
				// wav 文件中解析出的音频数据是 int16_t，需要加上 0x8000 转换成 uint16_t 发送给 dac
				for (uint32_t i = 0; i < PCM_BUFSIZE; ++i)
				{
					PP_Buff.buf[PP_Buff.pong * PCM_BUFSIZE + i] += 32768;
				}
				// 由于上述转换过程中，直接播放会有卡顿，切换 ping 和 pong 会有改善
				PP_Buff.ping = PP_Buff.ping ^ PP_Buff.pong;
				PP_Buff.pong = PP_Buff.pong ^ PP_Buff.ping;
				PP_Buff.ping = PP_Buff.ping ^ PP_Buff.pong;
				//切换 DAC_DMA 源
				DMA->CH[DMA_CH0].SRC = (uint32_t)&PP_Buff.buf[PP_Buff.ping * PCM_BUFSIZE];
#endif
			}
		}
	}
  
  //最后检测触摸退出
  //if(Boot_Video_Flag == true){
  //  Boot_Video_Flag = false; //处理完成
  //  return fsm_rt_cpl;
  //}
  
	return fsm_rt_on_going;
}

/**
  \brief		播放任务轮询
  \param [in]	mv		 : 播放任务句柄
  \return       0 : 空闲	1 : 忙碌	-1:抛出错误
  \note    		须轮询执行
 */
int play_mv_task_handler(play_mv_t *mv_task)
{
	switch (mv_task->fsm_state) //切换播放状态
	{
	case (fsm_rt_cpl):
		mv_task->fsm_state = close_mv(mv_task);
		// break;//continue
	case (fsm_rt_waiting):
		return 0;

	case (fsm_rt_start):
		mv_task->fsm_state = play_mv_init(mv_task, mv_task->path);
		// break;//continue
	case (fsm_rt_on_going):
		mv_task->fsm_state = play_mv(mv_task);
		return 1;

	default:
		MV_DEBUG("[ERROR] : Play MV ERROR!\r\n");
		return -1;
	}
	return 0;
}
